package org.vincen.helloworld.jaxb;

import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.EnumMemberValue;
import javassist.bytecode.annotation.StringMemberValue;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 简单封装JAXB属性
 * 
 * @author vincen
 */
public class JAXBHelper {

	private static final Logger log = LoggerFactory.getLogger(JAXBHelper.class);
	private Marshaller marshaller = null;

	/**
	 * 生成一个JAXBHelper对象
	 * 
	 * @param clazz
	 *            Class参数列表
	 */
	public JAXBHelper(Class<?>... clazz) {
		try {
			marshaller = JAXBContext.newInstance(clazz).createMarshaller();
		} catch (JAXBException e) {
			log.warn("获取JAXBHelper对象失败！", e);
		}
	}

	/**
	 * 简单封装JAXB，返回转换后的对象 如果需要以其他方式写出，调用这个方法。
	 * 如果想前台传String类型XML串，则调用buildMarshalString(Object o)
	 * 
	 * @param clazz
	 *            需要转换的POJO的类对象
	 * @return Marshaller
	 */
	public Marshaller buildMarshaller() {
		try {
			// 是否格式化生成的xml串
			marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
					Boolean.TRUE);
			// 编码格式
			marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
			// 是否生成xml头信息（<?xml version="1.0" encoding="UTF-8"
			// standalone="yes"?>）
			marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.FALSE);
		} catch (JAXBException e) {
			log.warn("获取JAXB转换对象失败！", e);
		}
		return marshaller;
	}

	/**
	 * 简单封装JAXB，将JAXB转换成的XML以String方式返回。
	 * 
	 * @param clazz
	 *            需要转换的POJO的类对象
	 * @param o
	 *            需要转换的对象
	 * @return String
	 */
	public String buildMarshalString(Object o) {
		StringWriter writer = new StringWriter();
		try {
			buildMarshaller().marshal(o, writer);
			writer.flush();
			writer.close();
		} catch (JAXBException e) {
			log.warn("将XML对象写入String失败！", e);
		} catch (IOException e) {
			log.warn("StringWriter流不能关闭", e);
		}
		return writer.toString();
	}

	public Marshaller getMarshaller() {
		return marshaller;
	}

	/**
	 * 根据原始的POJO类，生成带有JAXB注解的POJO类
	 * 
	 * @author vincen
	 */
	static class GenerateJAXBPOJO {

		/**
		 * 根据原始的POJO类，生成一个添加JAXB注解的POJO类。此方法会注解类的全部字段
		 * 
		 * @param qualifiedName
		 *            POJO类的全名
		 * @return 注解后的类
		 */
		public static Class<?> genFullPOJO(String qualifiedName) {
			CtClass ctClass = null;
			try {
				ctClass = ClassPool.getDefault().get(qualifiedName);
			} catch (NotFoundException e) {
				e.printStackTrace();
			}
			ClassFile classFile = ctClass.getClassFile();
			ConstPool constPool = classFile.getConstPool();
			AnnotationsAttribute rootAttr = new AnnotationsAttribute(constPool,
					AnnotationsAttribute.visibleTag);
			Annotation rootAnnotation = new Annotation(
					"javax.xml.bind.annotation.XmlRootElement", constPool);
			rootAttr.addAnnotation(rootAnnotation);
			classFile.addAttribute(rootAttr);
			Class<?> clazz2 = null;
			try {
				clazz2 = ctClass.toClass();
			} catch (CannotCompileException e) {
				log.warn("jvm中存在重复对象", e);
			}
			return clazz2;
		}

		/**
		 * 根据原始的POJO类，生成一个添加JAXB注解的POJO类。此方法会根据reverse参数
		 * 判断，若reverse == true，则choosed参数为不需要绑定为XML的字段；
		 * 若reverse == false，则choosed参数为需要绑定为XML的字段。
		 * 
		 * @param qualifiedName
		 *            POJO类的全名
		 * @param reverse
		 *            决定需要或者不需要choosed字段绑定为XML
		 * @param choosed
		 *            需要或者不需要绑定为XML的字段
		 * @return 注解后的类
		 */
		public static Class<?> genChoosedPOJO(String qualifiedName,
				boolean reverse, String... choosed) {
			CtClass ctClass = null;
			try {
				ctClass = ClassPool.getDefault().get(qualifiedName);
			} catch (NotFoundException e) {
				e.printStackTrace();
			}
			ClassFile classFile = ctClass.getClassFile();
			ConstPool constPool = classFile.getConstPool();
			CtField[] ctFields = ctClass.getDeclaredFields();
			FieldInfo fieldInfo = null;
			//类注解属性
			AnnotationsAttribute rootAttr = new AnnotationsAttribute(constPool,
					AnnotationsAttribute.visibleTag);
			//类的第一个注解@XmlRootElement
			Annotation rootAnnotation1 = new Annotation(
					"javax.xml.bind.annotation.XmlRootElement", constPool);
			rootAttr.addAnnotation(rootAnnotation1);
			//类的第二个注解@XmlAccessorType
			Annotation rootAnnotation2 = new Annotation(
					"javax.xml.bind.annotation.XmlAccessorType", constPool);
			//@XmlAccessorType这个注解有个Enum类型的参数
			EnumMemberValue emv = new EnumMemberValue(constPool);
			//设置XmlAccessType类型的枚举
			emv.setType("javax.xml.bind.annotation.XmlAccessType");
			//字段上的注解属性
			AnnotationsAttribute eleAttr = new AnnotationsAttribute(constPool,
					AnnotationsAttribute.visibleTag);
			//字段上的注解
			Annotation eleAnnotation = null;
			Arrays.sort(choosed);
			if (reverse) {
				emv.setValue("FIELD");
				eleAnnotation = new Annotation(
						"javax.xml.bind.annotation.XmlTransient", constPool);
			} else {
				emv.setValue("NONE");
				eleAnnotation = new Annotation(
						"javax.xml.bind.annotation.XmlElement", constPool);
			}
			rootAnnotation2.addMemberValue("value", emv);
			rootAttr.addAnnotation(rootAnnotation2);
			eleAttr.addAnnotation(eleAnnotation);
			for (CtField f : ctFields) {
				fieldInfo = f.getFieldInfo();
				if (Arrays.binarySearch(choosed, fieldInfo.getName()) >= 0) {
					fieldInfo.addAttribute(eleAttr);
				}
			}
			classFile.addAttribute(rootAttr);
			Class<?> clazz = null;
			try {
				clazz = ctClass.toClass();
			} catch (CannotCompileException e) {
				log.warn("jvm中存在重复对象", e);
			}
			return clazz;
		}

		/**
		 * 根据原始的POJO类，生成一个添加JAXB注解的POJO类。此方法会根据map中指定的
		 * 对应关系，将POJO类中字段的名字，用map.getValue()做为别名表示。使用此方法，
		 * 必须对POJO中所有字段重命名。
		 * 
		 * @param qualifiedName
		 *            POJO类的全名
		 * @param map
		 *            POJO类字段与其别名的映射
		 * @return
		 */
		public static Class<?> genChangedPOJO(String qualifiedName,
				Map<String, String> map) {
			CtClass ctClass = null;
			try {
				ctClass = ClassPool.getDefault().get(qualifiedName);
			} catch (NotFoundException e) {
				e.printStackTrace();
			}
			ClassFile classFile = ctClass.getClassFile();
			ConstPool constPool = classFile.getConstPool();
			CtField[] ctFields = ctClass.getDeclaredFields();
			FieldInfo fieldInfo = null;
			//在类上加注解
			AnnotationsAttribute rootAttr = new AnnotationsAttribute(constPool,
					AnnotationsAttribute.visibleTag);
			Annotation rootAnnotation1 = new Annotation(
					"javax.xml.bind.annotation.XmlRootElement", constPool);
			rootAttr.addAnnotation(rootAnnotation1);
			Annotation rootAnnotation2 = new Annotation(
					"javax.xml.bind.annotation.XmlAccessorType", constPool);
			EnumMemberValue emv = new EnumMemberValue(constPool);
			emv.setType("javax.xml.bind.annotation.XmlAccessType");
			emv.setValue("NONE");
			rootAnnotation2.addMemberValue("value", emv);
			rootAttr.addAnnotation(rootAnnotation2);
			classFile.addAttribute(rootAttr);
			//在字段上加注解
			AnnotationsAttribute eleAttr = new AnnotationsAttribute(constPool,
					AnnotationsAttribute.visibleTag);
			Annotation eleAnnotation = new Annotation(
					"javax.xml.bind.annotation.XmlElement", constPool);
			String[] choosed = new String[map.size()];
			Set<String> keys = map.keySet();
			keys.toArray(choosed);
			Arrays.sort(choosed);
			for (CtField f : ctFields) {
				fieldInfo = f.getFieldInfo();
				String fName = fieldInfo.getName();
				if (Arrays.binarySearch(choosed, fName) >= 0) {
					eleAnnotation.addMemberValue("name", new StringMemberValue(map.get(fName), constPool));
					eleAttr.addAnnotation(eleAnnotation);
					fieldInfo.addAttribute(eleAttr);
				}
			}
			Class<?> clazz2 = null;
			try {
				clazz2 = ctClass.toClass();
			} catch (CannotCompileException e) {
				log.warn("jvm中存在重复对象", e);
			}
			return clazz2;
		}
	}
}
